/*
 * Copyright (c) 2010, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "alkes/graph/Clippable.h"

namespace alkes {

Clippable::Clippable()
: rect_(Rect::ZERO)
, last_clipped_(Rect::ZERO)
{
    setSystemFlag(FLAG_HAS_SIZE);
}

bool Clippable::hasClipOffset() const
{
    return rect_.x != 0 || rect_.y != 0;
}

void Clippable::setRect(const Rect& rect)
{
    setProperty(rect_, rect);
}

void Clippable::setSize(const RectSize& size)
{
    setProperty(rect_.width, size.width);
    setProperty(rect_.height, size.height);
}

void Clippable::setOffset(const Point2D& offset)
{
    setProperty(rect_.x, offset.x);
    setProperty(rect_.y, offset.y);
}

const Rect& Clippable::getRect() const
{
    return rect_;
}

const RectSize Clippable::getSize() const
{
    RectSize size = {rect_.width, rect_.height};
    return size;
}

const Point2D Clippable::getOffset() const
{
    Point2D offset = {rect_.x, rect_.y};
    return offset;
}

void Clippable::disableClip(bool disable_clip)
{
    if (disable_clip != isDisableClip())
    {
        if (disable_clip)
            setSystemFlag(FLAG_DISABLE_CLIP);
        else
            clearSystemFlag(FLAG_DISABLE_CLIP);

        setDirty(true);
    }
}

bool Clippable::isDisableClip() const
{
    return systemFlag(FLAG_DISABLE_CLIP);
}

void Clippable::setDrawable(bool drawable)
{
    if (drawable != isDrawable())
    {
        if (drawable)
            setSystemFlag(FLAG_DRAWABLE);
        else
            clearSystemFlag(FLAG_DRAWABLE);

        setDirty(true);
    }
}

bool Clippable::onPreRender(GraphicsContext& gc)
{
    if (!isDrawable() && !hasVisibleChild())
    {
        last_clipped_.reset();
        return false;
    }

    IGraphics2DDevice* device = gc.getGraphics2DDevice();
    bool enable_clip = !this->isDisableClip();
    bool has_clip_offset = this->hasClipOffset();
    bool need_to_post_render = true;

    // calculate new scissor.
    if (has_clip_offset)
        gc.addOffset(rect_.x, rect_.y);

    // push this scissor to stack.
    if (enable_clip)
        gc.pushScissorSize(rect_.width, rect_.height);

    Rect rect = gc.getCurrentScissorRect();

    if (rect.isValidSize())
    {
        if (has_clip_offset)
        {
            device->pushMatrix();
            device->translate(rect_.x, rect_.y);
        }

        if (enable_clip)
        {
            device->setScissorRect(rect);
            last_clipped_ = rect;
        }
    }
    else
    {
        // resotre
        restoreScissorStack(gc);

        need_to_post_render = false;

        last_clipped_.reset();
    }

    return need_to_post_render;
}

void Clippable::onPostRender(GraphicsContext& gc)
{
    IGraphics2DDevice* device = gc.getGraphics2DDevice();
    bool enable_clip = !this->isDisableClip();
    bool has_clip_offset = this->hasClipOffset();

    if (enable_clip) {
        device->setScissorRect(gc.getPrevScissorRect());
    }

    if (has_clip_offset) {
        device->popMatrix();
    }

    restoreScissorStack(gc);
}

void Clippable::restoreScissorStack(GraphicsContext& gc)
{
    // restore scissor rect
    if (!this->isDisableClip())
        gc.popScissorRect();

    if (this->hasClipOffset())
        gc.minusOffset(rect_.x, rect_.y);
}

bool Clippable::onPreUpdate(UpdateContext& uc)
{
    if (getLastChild() == 0 && !isDirty())
        return false;

    if (hasClipOffset()) {
        uc.addOffset(rect_.x, rect_.y);
    }

    uc.pushScissorSize(rect_.width, rect_.height);

    // already dirty
    if (isDirty()) {

        uc.addDirtyRect(uc.getCurrentScissorRect());

        if (!last_clipped_.isNull()) {
            uc.addDirtyRect(last_clipped_);
        }

        uc.popScissorRect();

        if (hasClipOffset()) {
            uc.minusOffset(rect_.x, rect_.y);
        }
        return false;
    }

    return true;
}

bool Clippable::onPostUpdate(UpdateContext& uc, bool updated_child)
{
    if (updated_child || isDirty())
    {
        // Add dirty rect
        uc.addDirtyRect(uc.getCurrentScissorRect());

        if (isDirty() && !last_clipped_.isNull())
        {
            uc.addDirtyRect(last_clipped_);
        }
    }

    uc.popScissorRect();

    if (hasClipOffset())
        uc.minusOffset(rect_.x, rect_.y);

    return (this->isDisableClip() && uc.relayDirtyFlag(updated_child));
}

}
